﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SCMS.Model;
using System.Transactions;
using SCMS.CoreBusinessLogic.NotificationsManager;
using System.Data.Objects.DataClasses;
using SCMS.CoreBusinessLogic._ExchangeRate;
using SCMS.CoreBusinessLogic.Budgeting;

namespace SCMS.CoreBusinessLogic.SalaryPayment
{
    public class SalaryPaymentService : ISalaryPaymentService
    {
        private INotificationService notificationService;
        private IExchangeRateService exchangeRateService;
        private IBudgetService budgetService;

        public SalaryPaymentService(INotificationService notificationService, IExchangeRateService exchangeRateService, IBudgetService budgetService)
        {
            this.notificationService = notificationService;
            this.exchangeRateService = exchangeRateService;
            this.budgetService = budgetService;
        }

        public bool SaveSalaryReqst(SalaryBudgetline SPBLEntity, SalaryRequest SPEntity)
        {
            using (var dbContext = new SCMSEntities())
            {
                using (TransactionScope scope = new TransactionScope())
                {
                    try
                    {
                        if (SPEntity != null)
                        {
                            SPEntity.TotalPayroll = SPBLEntity.Amount;
                            dbContext.SalaryRequests.AddObject(SPEntity);
                            //if (dbContext.SaveChanges() > 0)
                            //{
                                SPBLEntity.Id = Guid.NewGuid();
                                dbContext.SalaryBudgetlines.AddObject(SPBLEntity);
                                if ((dbContext.SaveChanges() > 0))
                                {
                                    //NotificationServiceMail.SendNotification(NotificationServiceMail.GetApproverEmailAddress(1, NotifcationHelper.spmCode), NotifcationHelper.spMsgBody, NotifcationHelper.spsubject);
                                    scope.Complete();
                                    return true;
                                }
                                else { scope.Dispose(); return false; }
                            //}
                            //else { scope.Dispose(); return false; }
                        }
                        else
                        {
                            if (SPBLEntity.Id.Equals(Guid.Empty))
                            {

                                SPBLEntity.Id = Guid.NewGuid();
                                dbContext.SalaryBudgetlines.AddObject(SPBLEntity);
                                //if ((dbContext.SaveChanges() > 0))
                                //{
                                    Model.SalaryRequest SP = dbContext.SalaryRequests.First(p => p.Id == SPBLEntity.SalaryId);
                                    SP.TotalPayroll += SPBLEntity.Amount;
                                    dbContext.ObjectStateManager.ChangeObjectState(SP, System.Data.EntityState.Modified);
                                    if ((dbContext.SaveChanges() > 0)) { scope.Complete(); return true; } else { scope.Dispose(); return false; }
                                //}
                                //else { scope.Dispose(); return false; }
                            }
                            else
                            {
                                Model.SalaryRequest SP = dbContext.SalaryRequests.First(p => p.Id == SPBLEntity.SalaryId);
                                SP.TotalPayroll -= dbContext.SalaryBudgetlines.First(p => p.Id == SPBLEntity.Id).Amount;
                                SP.TotalPayroll += SPBLEntity.Amount;

                                //dbContext.SalaryBudgetlines.Attach(entity);
                                dbContext.ObjectStateManager.ChangeObjectState(SPBLEntity, System.Data.EntityState.Modified);
                                //if ((dbContext.SaveChanges() > 0))
                                //{
                                    //dbContext.SalaryRequest.Attach(or);
                                    dbContext.ObjectStateManager.ChangeObjectState(SP, System.Data.EntityState.Modified);
                                    if ((dbContext.SaveChanges() > 0)) { scope.Complete(); return true; } else { scope.Dispose(); return false; }
                                //}
                                //else { scope.Dispose(); return false; }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        scope.Dispose();
                        throw ex;
                    }
                }
            }
        }

        public string GenerateUniquNumber(CountryProgramme cp)
        {
            string code = "SPM/DRC/" + cp.Country.ShortName + "/";
            string refNumber = "";
            long count = 1;
            using (var dbContext = new SCMSEntities())
            {
                var total = dbContext.SalaryRequests.Where(p => p.CountryProgrammeId == cp.Id && p.IsSubmitted == true).Count();
                count = total;
                Model.SalaryRequest m = null;
                do
                {
                    count++;
                    if (count < 10000)
                    {
                        if (count < 10)
                            refNumber = code + "0000" + count;
                        if (count < 100 & count >= 10)
                            refNumber = code + "000" + count;
                        if (count < 1000 & count >= 100)
                            refNumber = code + "00" + count;
                        if (count < 10000 & count >= 1000)
                            refNumber = code + "0" + count;
                    }
                    m = dbContext.SalaryRequests.FirstOrDefault(p => p.RefNumber == refNumber);
                } while (m != null);
                return refNumber;
            }
        }

        public List<SalaryBudgetline> GetSPBLines(Guid SalaryId)
        {
            using (var db = new SCMSEntities())
            {
                List<SalaryBudgetline> SPBList = db.SalaryBudgetlines.Where(p => p.SalaryId == SalaryId).ToList();
                foreach (SalaryBudgetline item in SPBList)
                {
                    ProjectDonor pb = item.ProjectBudget.BudgetCategory.ProjectDonor;
                    PayrollItem pitm = item.PayrollItem;
                    SalaryRequest sr = item.SalaryRequest;
                }
                return SPBList;
            }
        }

        public List<SalaryRequest> GetSalaryPayments(Guid CPId)
        {
            using (var db = new SCMSEntities())
            {
                List<SalaryRequest> SPayment = db.SalaryRequests.Where(p => p.CountryProgrammeId == CPId).OrderByDescending(p => p.PreparedOn).ToList();
                foreach (SalaryRequest item in SPayment)
                {
                    Currency c = item.Currency;
                    EntityCollection<SalaryBudgetline> sbl = item.SalaryBudgetlines;
                    foreach (SalaryBudgetline sblitem in sbl)
                    {
                        ProjectDonor pb = sblitem.ProjectBudget.BudgetCategory.ProjectDonor;
                    }

                }
                return SPayment;
            }
        }

        public List<PayrollItem> GetPayrollItems(Guid CPid)
        {
            using (var db = new SCMSEntities())
            {
                return db.PayrollItems.Where(p => p.CountryProgramId == CPid).ToList();
            }
        }

        public List<SalaryRequest> GetSalaryPaymentsForReview(SystemUser currentUser)
        {
            try
            {
                using (var context = new SCMSEntities())
                {
                    List<Model.SalaryRequest> salaryPayments = new List<Model.SalaryRequest>();
                    context.SystemUsers.Attach(currentUser);
                    var approvers = context.Approvers.Where(a => a.UserId == currentUser.Id && a.ActivityCode == NotificationHelper.spmCode && a.ActionType == NotificationHelper.reviewCode).ToList();
                    var delegateApprovers = context.Approvers.Where(a => a.AssistantId == currentUser.Id && a.ActivityCode == NotificationHelper.spmCode && a.ActionType == NotificationHelper.reviewCode).ToList();
                    if (approvers != null)
                    {
                        foreach (var approver in approvers)
                        {
                            var spmList = context.SalaryRequests.Where(e => e.CountryProgrammeId == currentUser.Staff.CountrySubOffice.CountryProgrammeId && (e.IsSubmitted == true && e.IsReviewed == false && e.IsRejected == false)
                                && e.Notifications.Where(n => (Guid)n.SalaryPaymentId == e.Id && n.IsRespondedTo == false && n.SentToDelegate == false && n.ApproverId == approver.Id).Count() > 0).ToList();
                            foreach (var spm in spmList)
                            {
                                salaryPayments.Add(spm);
                            }
                        }
                    }
                    if (delegateApprovers != null)
                    {
                        foreach (var delegateApprover in delegateApprovers)
                        {
                            var spmList = context.SalaryRequests.Where(e => e.CountryProgrammeId == currentUser.Staff.CountrySubOffice.CountryProgrammeId && (e.IsSubmitted == true && e.IsReviewed == false && e.IsRejected == false)
                                && e.Notifications.Where(n => (Guid)n.SalaryPaymentId == e.Id && n.IsRespondedTo == false && n.SentToDelegate == true && n.ApproverId == delegateApprover.Id).Count() > 0).ToList();
                            foreach (var spm in spmList)
                            {
                                salaryPayments.Add(spm);
                            }
                        }
                    }
                    return salaryPayments;
                }
            }
            catch (Exception ex)
            {
                return new List<Model.SalaryRequest>();
            }
        }

        public List<SalaryRequest> GetSalaryPaymentsForAuth(SystemUser currentUser)
        {
            try
            {
                using (var context = new SCMSEntities())
                {
                    List<Model.SalaryRequest> salaryPayments = new List<Model.SalaryRequest>();
                    context.SystemUsers.Attach(currentUser);
                    var approvers = context.Approvers.Where(a => a.UserId == currentUser.Id && a.ActivityCode == NotificationHelper.spmCode && a.ActionType == NotificationHelper.authorizationCode).ToList();
                    var delegateApprovers = context.Approvers.Where(a => a.AssistantId == currentUser.Id && a.ActivityCode == NotificationHelper.spmCode && a.ActionType == NotificationHelper.authorizationCode).ToList();
                    if (approvers != null)
                    {
                        foreach (var approver in approvers)
                        {
                            var spmList = context.SalaryRequests.Where(s => s.CountryProgrammeId == currentUser.Staff.CountrySubOffice.CountryProgrammeId && (s.IsReviewed == true && s.IsAuthorized == false)
                                && s.Notifications.Where(n => (Guid)n.SalaryPaymentId == s.Id && n.IsRespondedTo == false && n.SentToDelegate == false && n.ApproverId == approver.Id).Count() > 0).ToList();
                            foreach (var spm in spmList)
                            {
                                salaryPayments.Add(spm);
                            }
                        }
                    }
                    if (delegateApprovers != null)
                    {
                        foreach (var delegateApprover in delegateApprovers)
                        {
                            var spmList = context.SalaryRequests.Where(s => s.CountryProgrammeId == currentUser.Staff.CountrySubOffice.CountryProgrammeId && (s.IsReviewed == true && s.IsAuthorized == false)
                                && s.Notifications.Where(n => (Guid)n.SalaryPaymentId == s.Id && n.IsRespondedTo == false && n.SentToDelegate == true && n.ApproverId == delegateApprover.Id).Count() > 0).ToList();
                            foreach (var spm in spmList)
                            {
                                salaryPayments.Add(spm);
                            }
                        }
                    }
                    return salaryPayments;
                }
            }
            catch (Exception ex)
            {
                return new List<Model.SalaryRequest>();
            }
        }

        public List<SalaryRequest> GetSalaryPaymentsForPosting(Guid countryProgId, SystemUser currentUser)
        {
            using (var context = new SCMSEntities())
            {
                var approvers = context.Approvers.Where(a => a.CountryProgrammeId == countryProgId && a.ActivityCode == NotificationHelper.spmCode && a.ActionType == NotificationHelper.postFundsCode
                    && (a.UserId == currentUser.Id || a.AssistantId == currentUser.Id));
                var financeLimit = context.SystemUsers.FirstOrDefault(s => s.Id == currentUser.Id).Staff.FinanceLimit;
                if (financeLimit == null)
                    return new List<SalaryRequest>();
                if (approvers.ToList().Count == 0)
                    return new List<SalaryRequest>();
                if (approvers.Where(a => a.ProjectDonorId == null).Count() > 0)
                    return context.SalaryRequests.Where(s => s.CountryProgrammeId == countryProgId && s.IsAuthorized == true && s.FundsPosted == false
                        && (s.MBTotal.Value <= financeLimit.Limit || financeLimit.Limit == 0)).ToList();
                List<SalaryRequest> salaryRequests = new List<SalaryRequest>();
                foreach (var approver in approvers.ToList())
                {
                    var spms = context.SalaryRequests.Where(s => s.CountryProgrammeId == countryProgId && s.IsAuthorized == true && s.FundsPosted == false
                        && (s.MBTotal.Value <= financeLimit.Limit || financeLimit.Limit == 0)
                        && s.SalaryBudgetlines.Where(i => i.ProjectBudget.BudgetCategory.ProjectDonorId.Value == approver.ProjectDonorId.Value).Count() > 0).ToList();
                    foreach (var spm in spms)
                    {
                        if (!SpmExistsInList(spm, salaryRequests))
                            salaryRequests.Add(spm);
                    }
                }
                return salaryRequests;
            }
        }

        private bool SpmExistsInList(SalaryRequest spm, List<SalaryRequest> spmList)
        {
            foreach (var e in spmList)
            {
                if (e.Equals(spm))
                    return true;
            }
            return false;
        }

        public SalaryRequest GetSalaryRequestById(Guid Id)
        {
            using(var context = new SCMSEntities())
            {
                var spm = context.SalaryRequests.FirstOrDefault(s => s.Id == Id);
                var location = spm.CountrySubOffice.Location;
                var currency = spm.Currency;
                var person = spm.Staff2.Person;
                var desg = spm.Staff2.Designation;
                if (spm.Staff != null)
                {
                    person = spm.Staff.Person;
                    desg = spm.Staff.Designation;
                }
                else if (spm.Staff1 != null)
                {
                    person = spm.Staff1.Person;
                    desg = spm.Staff1.Designation;
                }
                else if (spm.Staff3 != null)
                {
                    person = spm.Staff3.Person;
                    desg = spm.Staff3.Designation;
                }
                return spm;
            }
        }

        public SalaryBudgetline GetSalaryBudgetLineById(Guid Id)
        {
            using (var context = new SCMSEntities())
            {
                var sbl = context.SalaryBudgetlines.IncludeSalaryRequest().IncludeProjectDonor().FirstOrDefault(s => s.Id == Id);
                return sbl;
            }
        }
        
        public bool SaveSalaryRequest(SalaryRequest spm)
        {
            using (var context = new SCMSEntities())
            {
                if (spm.Id != Guid.Empty)
                {
                    context.SalaryRequests.Attach(spm);
                    context.ObjectStateManager.ChangeObjectState(spm, System.Data.EntityState.Modified);
                }
                else
                {
                    spm.Id = Guid.NewGuid();
                    context.SalaryRequests.AddObject(spm);
                }
                return context.SaveChanges() > 0;
            }
        }

        public bool SaveSalaryBudgetLine(SalaryBudgetline sbl)
        {
            using (var context = new SCMSEntities())
            {
                if (sbl.Id != Guid.Empty)
                {
                    context.SalaryBudgetlines.Attach(sbl);
                    context.ObjectStateManager.ChangeObjectState(sbl, System.Data.EntityState.Modified);
                }
                else
                {
                    var existingBL = context.SalaryBudgetlines.FirstOrDefault(b => b.SalaryId == sbl.SalaryId && b.PayrollItemId == sbl.PayrollItemId && b.BudgetLineId == sbl.BudgetLineId);
                    if (existingBL != null)
                    {
                        existingBL.Amount += sbl.Amount;
                    }
                    else
                    {
                        sbl.Id = Guid.NewGuid();
                        context.SalaryBudgetlines.AddObject(sbl);
                    }
                }
                return context.SaveChanges() > 0;
            }
        }
        public bool CommitFunds(SalaryRequest spm)
        {
            decimal commitAmount = 0;
            using (var context = SCMSEntities.Define())
            {
                using (TransactionScope scope = new TransactionScope())
                {
                    try
                    {
                        var spmItems = context.SalaryBudgetlines.Where(a => a.SalaryId == spm.Id).ToList();
                        foreach (var spmItem in spmItems)
                        {
                            commitAmount = (decimal)exchangeRateService.GetForeignCurrencyValue(spmItem.ProjectBudget.BudgetCategory.ProjectDonor.Currency, spmItem.SalaryRequest.Currency, spmItem.Amount, spmItem.SalaryRequest.CountryProgrammeId);
                            var bc = new BudgetCommitment();
                            bc.Id = Guid.NewGuid();
                            bc.AmountCommitted = commitAmount;
                            bc.BudgetLineId = spmItem.BudgetLineId;
                            bc.DateCommitted = DateTime.Now;
                            bc.SalaryBudgetLineId = spmItem.Id;
                            context.BudgetCommitments.AddObject(bc);
                            spmItem.ProjectBudget.TotalCommitted += commitAmount;
                        }
                        if (context.SaveChanges() > 0)
                        {
                            scope.Complete();
                            return true;
                        }
                        else
                        {
                            scope.Dispose();
                            return false;
                        }
                    }
                    catch (Exception ex)
                    {
                        scope.Dispose();
                        throw ex;
                    }
                }
            }
        }

        public bool EffectPosting(SalaryRequest spm, Staff poster)
        {
            using (var context = SCMSEntities.Define())
            {
                using (TransactionScope scope = new TransactionScope())
                {
                    try
                    {
                        var spmCommits = context.BudgetCommitments.Where(b => b.SalaryBudgetline.SalaryId == spm.Id).ToList();
                        foreach (var spmCommit in spmCommits)
                        {
                            var pb = spmCommit.ProjectBudget;
                            pb.TotalCommitted -= spmCommit.AmountCommitted;

                            var bp = new BudgetPosting();
                            bp.Id = Guid.NewGuid();
                            bp.AmountPosted = spmCommit.AmountCommitted;
                            bp.DatePosted = DateTime.Now;
                            bp.PostedBy = poster.Id;
                            bp.SalaryBudgetLineId = spmCommit.SalaryBudgetLineId;
                            pb.TotalPosted += bp.AmountPosted;

                            //Delete commitment and add posting
                            context.BudgetCommitments.DeleteObject(spmCommit);
                            context.BudgetPostings.AddObject(bp);
                        }

                        var salaryPayment = context.SalaryRequests.FirstOrDefault(a => a.Id == spm.Id);
                        salaryPayment.FundsPosted = true;
                        salaryPayment.PostedBy = poster.Id;
                        salaryPayment.PostedOn = DateTime.Now;

                        if (context.SaveChanges() > 0)
                        {                            
                            scope.Complete();
                            return true;
                        }
                        else
                        {
                            scope.Dispose();
                            return false;
                        }
                    }
                    catch (Exception ex)
                    {
                        scope.Dispose();
                        throw ex;
                    }
                }
            }
        }

        // <summary>
        /// Send notification to requestor and project managers of the affected budget lines
        /// </summary>
        /// <param name="rfa"></param>
        public void NotifyAffected(SalaryRequest spm)
        {
            using (var context = new SCMSEntities())
            {
                //Get ProjectBLCount list order by highest budget line count to least for this RFA
                List<ProjectBLCount> projects = context.SalaryBudgetlines.Where(s => s.SalaryId == spm.Id).GroupBy(bl => bl.ProjectBudget.BudgetCategory.ProjectDonor).
                    Select(projList => new ProjectBLCount
                    {
                        ProjectDonorId = projList.FirstOrDefault().ProjectBudget.BudgetCategory.ProjectDonor.Id,
                        BudgetLineCount = projList.Count()
                    }).OrderByDescending(p => p.BudgetLineCount).ToList<ProjectBLCount>();
                //Notify requestor
                var requestor = context.Staffs.FirstOrDefault(s => s.Id == spm.PreparedBy).Person;
                var msgBody = string.Format(NotificationHelper.spmFundsPostedMsgBody, requestor.FirstName, spm.RefNumber);
                notificationService.SendNotification(requestor.OfficialEmail, msgBody, NotificationHelper.spmsubject);

                //Notify the Project Manager(s)
                foreach (var project in projects)
                {
                    var proj = context.ProjectDonors.FirstOrDefault(p => p.Id == project.ProjectDonorId);
                    var name = proj.Staff.Person.FirstName;
                    var email = proj.Staff.Person.OfficialEmail;
                    msgBody = string.Format(NotificationHelper.spmPMNotifyFundsPostedMsgBody, name, spm.RefNumber, proj.ProjectNumber);
                    notificationService.SendNotification(email, msgBody, NotificationHelper.spmsubject);
                }
            }
        }

        public void DeleteSPMBudgetLineById(Guid Id)
        {
            using (var context = new SCMSEntities())
            {
                var spmItem = context.SalaryBudgetlines.FirstOrDefault(b => b.Id == Id);
                if (spmItem != null)
                {
                    spmItem.SalaryRequest.TotalPayroll -= spmItem.Amount;
                    context.SalaryBudgetlines.DeleteObject(spmItem);
                }
                context.SaveChanges();
            }
        }

        public void DeleteSPMById(Guid Id)
        {
            using (var context = new SCMSEntities())
            {
                var spm = new SalaryRequest { Id = Id };
                context.SalaryRequests.Attach(spm);
                context.ObjectStateManager.ChangeObjectState(spm, System.Data.EntityState.Deleted);
                context.SaveChanges();
            }
        }

        public List<BudgetCheckResult> RunFundsAvailableCheck(Guid spmId)
        {
            var bcrList = new List<BudgetCheckResult>();
            using (var context = new SCMSEntities())
            {
                var spmItems = context.SalaryBudgetlines.IncludeSalaryRequest().IncludeProjectDonor().Where(r => r.SalaryId == spmId);
                var spm = spmItems.ToList()[0].SalaryRequest;
                //Construct list of project budgets for that OR
                List<ProjectBudget> pbList = new List<ProjectBudget>();
                foreach (var ecfItem in spmItems.ToList())
                {
                    if (!pbList.Contains(ecfItem.ProjectBudget))
                        pbList.Add(ecfItem.ProjectBudget);
                }
                foreach (var pb in pbList)
                {
                    decimal totalAmount = spmItems.Where(b => b.BudgetLineId == pb.Id).Sum(b => b.Amount);
                    if (!budgetService.SufficientFundsAvailable(totalAmount, pb.Id.ToString(), spm.SalaryCurrencyId.ToString(), Guid.Empty, Guid.Empty, Guid.Empty))
                    {
                        decimal availableFunds = pb.TotalBudget - (decimal)(pb.TotalCommitted + pb.TotalPosted);
                        availableFunds = (decimal)exchangeRateService.GetForeignCurrencyValue(spm.SalaryCurrencyId, pb.BudgetCategory.ProjectDonor.CurrencyId.Value, availableFunds, spm.CountryProgrammeId);
                        bcrList.Add(new BudgetCheckResult { ProjectBudget = pb, AmountChecked = totalAmount, AvailableAmount = availableFunds, FundsAvailable = false });
                    }
                }
            }
            return bcrList;
        }
    }
}
